/**
 * Module dependencies
 */

let ExpressOIDC = require('@okta/oidc-middleware').ExpressOIDC;



/**
 * Okta SSO  hook
 */

module.exports = function (sails){

  return {

    defaults: {
      oktaSSO: {
        // Default to look for a model w/ identity 'user'
        userModelIdentity: 'user'
      }
    },

    initialize: function (cb) {
      if(!sails.config.custom.oktaClientSecret){
        return cb();
      }
      sails.log('Okta SSO enabled. The built-in authorization mechanism will be disabled.');
      // Throw errors if required config variables are missing.
      if(!sails.config.custom.oktaOrgUrl){
        throw new Error(`Missing config! No sails.config.custom.oktaClientId was configured. To replace this app's built-in authorization mechanism with Okta SSO, a oktaClientId value is required.`);
      }
      if(!sails.config.custom.oktaClientSecret){
        throw new Error(`Missing config! No sails.config.custom.oktaClientSecret was configured. To replace this app's built-in authorization mechanism with Okta SSO, a oktaClientSecret value is required.`);
      }

      // Clone the existing routes
      // NOTE: Changing sails.config after the app lifts goes against Sails.js conventions and this code should not be reproduced.
      let appRoutes = Object.assign({}, sails.config.routes);
      // Remove the routes for the built-in login page..
      delete appRoutes['GET /login'];
      sails.config.routes = Object.assign({}, appRoutes);
      sails.config.http.middleware.order = [
        'cookieParser',
        'session',
        'bodyParser',
        'compress',
        'poweredBy',
        'www',// Note: This changes the conventions of Sails.js. Don't ever replicate this or use Passport.js.
        'oktaSSO',
        'router',
        'favicon',
      ];
      // Specify a custom http middleware order, placing the Okta middleware before the router. This is so the routes generated by Okta will take precedence over the sails router.
      // Create a passport instance to use
      sails.config.http.middleware.oktaSSO = (function() {
        let oidc = new ExpressOIDC({
          issuer: sails.config.custom.oktaOrgUrl,
          client_id: sails.config.custom.oktaClientId,//eslint-disable-line camelcase
          client_secret: sails.config.custom.oktaClientSecret,//eslint-disable-line camelcase
          appBaseUrl: sails.config.custom.baseUrl,
          scope: 'openid profile',
          routes: {
            login: {
              path: '/login',
            },
            loginCallback: {
              path: '/authorization-code/callback',
              afterCallback: '/entrance/signup-okta-user-or-redirect',
            },
            logout: {
              path: '/okta/logout'
            },
            logoutCallback: {
              path: '/logout'
            }
          },
        });
        return oidc.router;
      })();

      var err;
      // Validate `userModelIdentity` config
      if (typeof sails.config.oktaSSO.userModelIdentity !== 'string') {
        sails.config.oktaSSO.userModelIdentity = 'user';
      }
      sails.config.oktaSSO.userModelIdentity = sails.config.oktaSSO.userModelIdentity.toLowerCase();



      // We must wait for the `orm` hook before acquiring our user model from `sails.models`
      // because it might not be ready yet.
      if (!sails.hooks.orm) {
        err = new Error();
        err.code = 'E_HOOK_INITIALIZE';
        err.name = 'Okta SSO Hook Error';
        err.message = 'The "Okta SSO" hook depends on the "orm" hook- cannot load the "Okta SSO" hook without it!';
        return cb(err);
      }
      sails.after('hook:orm:loaded', ()=>{

        // Look up configured user model
        var UserModel = sails.models[sails.config.oktaSSO.userModelIdentity];

        if (!UserModel) {
          err = new Error();
          err.code = 'E_HOOK_INITIALIZE';
          err.name = 'Okta SSO Hook Error';
          err.message = 'Could not load the Okta SSO hook because `sails.config.passport.userModelIdentity` refers to an unknown model: "'+sails.config.oktaSSO.userModelIdentity+'".';
          if (sails.config.oktaSSO.userModelIdentity === 'user') {
            err.message += '\nThis option defaults to `user` if unspecified or invalid- maybe you need to set or correct it?';
          }
          return cb(err);
        }

        // It's very important to trigger this callback method when you are finished
        // with the bootstrap!  (otherwise your server will never lift, since it's waiting on the bootstrap)
        cb();

      });

    },

    routes:{
      before: {
        '/*':{
          skipAssets: true,
          fn: function configureOktaSSO(req, res, next) {
            if(sails.config.custom.oktaClientSecret){
              req = _extendReq(req);
            }
            next();
          }
        }
      }
    }
  };
};






/**
 * Normally these methods are added to the global HTTP IncomingMessage
 * prototype, which breaks encapsulation of Passport core.
 * This function is a patch to override this and also attach them to the local req/res.
 * This allows these methods to work for incoming socket requests.
 * @param  {[type]} req [description]
 * @return {[type]}     [description]
 */
function _extendReq(req) {

  /**
   * Terminate an existing login session.
   *
   * @api public
   */
  req.logout =
  req.logOut = function() {
    if (req.session.passport) {
      delete req.session.passport;
    }
  };

  /**
   * Test if request is authenticated.
   *
   * @return {Boolean}
   * @api public
   */
  req.isAuthenticated = function() {
    if(req.session.passport) {
      if (req.session.passport.user) {
        if(req.session.passport.user.tokens) {
          let tokenExpiresAt = (req.session.passport.user.tokens.expires_at * 1000);
          if(tokenExpiresAt > Date.now()){
            return true;
          }
        }
      }
    }
    return false;
  };

  /**
   * Test if request is unauthenticated.
   *
   * @return {Boolean}
   * @api public
   */
  req.isUnauthenticated = function() {
    return !req.isAuthenticated();
  };

  return req;
}
